package il.technion.cs236369.webserver;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.Socket;
import java.util.Queue;

import org.apache.http.Header;
import org.apache.http.HttpException;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.impl.DefaultHttpServerConnection;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpDateGenerator;
import org.apache.http.protocol.HttpRequestHandler;

public class RequestHandler extends Thread
{
	//a reference to the requests queue
	Queue<ExtendedHttpRequest> requests = null;
	//each handler receives a serial number (for debug purpose)
	int handlerNumber = 0;

	public RequestHandler(Queue<ExtendedHttpRequest> requestsQueue, int number)
	{
		requests = requestsQueue;
		handlerNumber = number;
	}

	private boolean checkForConvertXMLtoJSONHeader(ExtendedHttpRequest request)
	{
		for (Header h: request.GetHttpRequest().getAllHeaders())
		{
			if (h.getName().contains("Convert") && h.getValue().contains("xml/json"))
			{
				return true;
			}
		}
		return false;
	}

	/**
	 * This method receives a request, handles it, and closes it
	 * @param request
	 */
	private void handleRequest(ExtendedHttpRequest request) 
	{
		Socket socket = request.GetSocket();
		HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, 200, "OK");
		response.setHeader(HttpHeaders.DATE, new HttpDateGenerator().getCurrentDate());
		response.setHeader(HttpHeaders.CONNECTION, HTTP.CONN_CLOSE);

		// check that if convert:xml/json header exist than the type is xml 
		if (checkForConvertXMLtoJSONHeader(request) && 
				!WebServer.map.getContentType(request).equals("xml"))
		{
			HttpResponser responser = new HttpResponser();
			responser.sendErrorResponse(socket, 400, "Bad Request", 
										"Convert xml/json header was in the request");
		}
		else
		{
			// initiate a session based on request
			Integer id = SessionManager.initiateSession(request, response);
			Session session = SessionManager.getSession(id);
			boolean sessionWasntEmpty = !session.isEmpty();
			String servlet = WebServer.map.getServletType(Mappings.getServletPath(request));

			// Check if a specific servlet exist, if not call default servlet
			if (servlet != null)
			{
				HttpContext context = request.GetContext();
				session.EqualiseWith(context);
				
				try
				{
					HttpRequestHandler handler = 
							(HttpRequestHandler)Class.forName(servlet).getConstructor().newInstance();
					handler.handle(request.GetHttpRequest(), response, context);
				}
				catch (InstantiationException | IllegalAccessException
						| IllegalArgumentException | InvocationTargetException
						| NoSuchMethodException | SecurityException
						| ClassNotFoundException | HttpException | IOException e)
				{
					System.err.print("RequestHandler.handleRequest: Error in getting servlet - ");
					System.err.println(e.getMessage());
					e.printStackTrace();
				}
			} 
			else
			{
				response = DefaultHandler.handle(request);
			}

			if (sessionWasntEmpty && session.isEmpty()) 
			{
				SessionManager.invalidateSession(id);
				String str = "id=0; expires=Thu, 01-Jan-1970 00:00:01 GMT;";
				response.setHeader("Set-Cookie", str);
			}
			else
			{
				response.setHeader("Set-Cookie", "id=" + id.toString());
			}

			DefaultHttpServerConnection conn = request.GetServerConn();

			try
			{
				conn.sendResponseHeader(response);
				conn.sendResponseEntity(response);
				conn.close();
			}
			catch (IOException e)
			{
				System.err.print("RequestHandler.handleRequest: IO Error - ");
				System.err.println(e.getMessage());
				e.printStackTrace(System.err);
			}
			catch (HttpException e)
			{
				System.err.print("RequestHandler.handleRequest: HTTP Error - ");
				System.err.println(e.getMessage());
				e.printStackTrace(System.err);
			}
			
			try
			{
				socket.close();
			} 
			catch (IOException e) 
			{
				System.err.println("RequestHandler.handleRequest: Handler unable to close socket.");
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * This method continuously listen to the request queue, extract request and handles them
	 */
	public void run()
	{
		ExtendedHttpRequest request = null;
		
		while (true)
		{
			synchronized (requests)
			{
				while (requests.isEmpty())
				{
					try
					{
						requests.wait();
					} 
					catch (InterruptedException e)
					{
						System.err.println("RequestHandler.run: Thread " + handlerNumber + " was intrupted");
					}
				}
				request = requests.remove();
			}
			
			handleRequest(request);
		}
	}
	
}
